package me.zhengjie.service;


import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhengjie.dto.Web3jDto;
import me.zhengjie.exception.BlockChainBaseException;
import me.zhengjie.utils.Convert;
import me.zhengjie.utils.EthUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.FunctionReturnDecoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.*;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.abi.datatypes.generated.Uint8;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.RawTransaction;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.request.Transaction;
import org.web3j.protocol.core.methods.response.EthCall;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@Slf4j
@Service
@AllArgsConstructor
public class Erc20Service {

    private final Web3jService web3jService;
    private final BlockChainService blockChainService;

    /**
     * erc20 公共调用方法
     * @param web3jDto
     * @param function
     * @param owner
     * @param erc20Address
     * @return
     */
    private String erc20Function(Web3jDto web3jDto, Function function, String owner, String erc20Address) {
        try {
            List<Type> retList = web3jService.callFunction(web3jDto, function, owner, erc20Address);
            if (retList == null) {
                retList = new ArrayList<>(0);
            }
            return retList.size() == 0 ? "" : retList.get(0).getValue().toString();
        } catch (Exception e) {
            String message = "erc20Function error! funtion=" + function.toString();
            throw new BlockChainBaseException(message);
        }
    }

    /**
     * 查询账户的通证余额
     * @param web3jDto
     * @param owner
     * @param erc20Address
     * @return
     */
    public BigDecimal balanceOf(Web3jDto web3jDto, String owner, String erc20Address) {
        String methodName = "balanceOf";
        List<Type> inputParameters = Arrays.asList(new Address(owner));
        List<TypeReference<?>> outputParameters = new ArrayList<>(0);
        TypeReference<Uint256> typeReference = new TypeReference<Uint256>() {
        };
        outputParameters.add(typeReference);
        Function function = new Function(methodName, inputParameters, outputParameters);

        String value = erc20Function(web3jDto, function, owner, erc20Address);
        if (StringUtils.isNotEmpty(value)) {
            return Convert.fromWei(new BigDecimal(value), Convert.Unit.fromString(decimals(web3jDto, erc20Address).toString()));
        } else {
            return BigDecimal.ZERO;
        }
    }

    /**
     * 查询代币符号
     * @param web3jDto
     * @param erc20Address
     * @return
     */
    public String symbol(Web3jDto web3jDto, String erc20Address) {
        String methodName = "symbol";
        List<TypeReference<?>> outputParameters = new ArrayList<>(0);
        TypeReference<Utf8String> typeReference = new TypeReference<Utf8String>() {
        };
        outputParameters.add(typeReference);
        Function function = new Function(methodName, new ArrayList<>(0), outputParameters);
        return erc20Function(web3jDto, function, null, erc20Address);
    }

    /**
     * 查询代币名称
     * @param web3jDto
     * @param erc20Address
     * @return
     */
    public String name(Web3jDto web3jDto, String erc20Address) {
        String methodName = "name";
        List<TypeReference<?>> outputParameters = new ArrayList<>(0);
        TypeReference<Utf8String> typeReference = new TypeReference<Utf8String>() {
        };
        outputParameters.add(typeReference);
        Function function = new Function(methodName, new ArrayList<>(0), outputParameters);
        return erc20Function(web3jDto, function, null, erc20Address);
    }

    /**
     * 查询代币精度
     * @param web3jDto
     * @param erc20Address
     * @return
     */
    public BigInteger decimals(Web3jDto web3jDto, String erc20Address) {
        String methodName = "decimals";
        List<TypeReference<?>> outputParameters = new ArrayList<>(1);
        TypeReference<Uint8> typeReference = new TypeReference<Uint8>() {
        };
        outputParameters.add(typeReference);
        Function function = new Function(methodName, new ArrayList<>(0), outputParameters);
        String value = erc20Function(web3jDto, function, null, erc20Address);
        return StringUtils.isNotEmpty(value) ? new BigInteger(value) : BigInteger.ZERO;
    }

    /**
     * 查询发行总量
     * @param web3jDto
     * @param erc20Address
     * @return
     */
    public BigInteger totalSupply(Web3jDto web3jDto, String erc20Address) {
        String methodName = "totalSupply";
        List<TypeReference<?>> outputParameters = new ArrayList<>(1);
        TypeReference<Uint256> typeReference = new TypeReference<Uint256>() {
        };
        outputParameters.add(typeReference);
        Function function = new Function(methodName, new ArrayList<>(0), outputParameters);
        String value = erc20Function(web3jDto, function, null, erc20Address);
        return StringUtils.isNotEmpty(value) ? Convert.fromWei(new BigDecimal(value), Convert.Unit.fromString(decimals(web3jDto, erc20Address).toString())).toBigInteger() : BigInteger.ZERO;
    }

    /**
     * 查询授权量
     * @param owner
     * @param spender
     * @return
     * @throws Throwable
     */
    public BigDecimal allowance(Web3jDto web3jDto, String erc20Address, String owner, String spender) {
        String methodName = "allowance";
        List<Type> inputParameters = Arrays.asList(
                new Address(owner),
                new Address(spender)
        );
        List<TypeReference<?>> outputParameters = Arrays.asList(
                new TypeReference<Uint256>() {
                }
        );
        Function function = new Function(methodName, inputParameters, outputParameters);
        String value = erc20Function(web3jDto, function, owner, erc20Address);
        return StringUtils.isNotEmpty(value) ? new BigDecimal(value) : BigDecimal.ZERO;
    }

    /**
     * 授权
     * @param web3jDto
     * @param erc20Address
     * @param fromPriKey
     * @param to
     * @param amount
     * @return
     */
    public String approve(Web3jDto web3jDto, String erc20Address, String fromPriKey, String to, BigDecimal amount) {
        java.util.function.Function<BigInteger, RawTransaction> function = getApprove(web3jDto, erc20Address, to, amount);
        Credentials credentials = blockChainService.getCredentials(fromPriKey);
        return web3jService.sendTransaction(web3jDto, credentials, function);
    }

    /**
     * 封装授权方法
     * @param web3jDto
     * @param erc20Address
     * @param to
     * @param amount
     * @return
     */
    private java.util.function.Function<BigInteger, RawTransaction> getApprove(Web3jDto web3jDto, String erc20Address, String to, BigDecimal amount) {
        java.util.function.Function<BigInteger, RawTransaction> function = (nonce) -> {
            String methodName = "approve";

            //将金额转化为以WEI为单位
            BigInteger value = Convert.toWei(amount, Convert.Unit.fromString(decimals(web3jDto, erc20Address).toString())).toBigInteger();

            //构建调用erc20合约的data
            List<Type> inputParameters = Arrays.asList(
                    new Address(to),
                    new Uint256(value)
            );

            String data = EthUtils.builderData(methodName, inputParameters, Collections.emptyList());
            //创建交易
            return RawTransaction.createTransaction(nonce, web3jDto.getGasPrice(), web3jDto.getGasLimit(), erc20Address, data);
        };
        return function;
    }

    /**
     * 转账
     * @param web3jDto
     * @param erc20Address
     * @param fromPriKey
     * @param to
     * @param amount
     * @return
     */
    public String transfer(Web3jDto web3jDto, String erc20Address, String fromPriKey, String to, BigDecimal amount) {
        java.util.function.Function<BigInteger, RawTransaction> function = getTransfer(web3jDto, erc20Address, to, amount);
        Credentials credentials = blockChainService.getCredentials(fromPriKey);
        return web3jService.sendTransaction(web3jDto, credentials, function);
    }

    /**
     * 转账封装方法
     * @param web3jDto
     * @param erc20Address
     * @param to
     * @param amount
     * @return
     */
    private java.util.function.Function<BigInteger, RawTransaction> getTransfer(Web3jDto web3jDto, String erc20Address, String to, BigDecimal amount) {
        java.util.function.Function<BigInteger, RawTransaction> function = (nonce) -> {
            String methodName = "transfer";

            //将金额转化为以WEI为单位
            BigInteger value = Convert.toWei(amount, Convert.Unit.fromString(decimals(web3jDto, erc20Address).toString())).toBigInteger();

            //构建调用erc20合约的data
            List<Type> inputParameters = Arrays.asList(
                    new Address(to),
                    new Uint256(value)
            );

            String data = EthUtils.builderData(methodName, inputParameters, Collections.emptyList());
            //创建交易
            return RawTransaction.createTransaction(nonce, web3jDto.getGasPrice(), web3jDto.getGasLimit(), erc20Address, data);
        };
        return function;
    }

    /**
     * 转账
     * @param web3jDto
     * @param erc20Address
     * @param fromPriKey
     * @param to
     * @param amount
     * @return
     */
    public String transferFrom(Web3jDto web3jDto, String erc20Address, String fromPriKey, String to, BigDecimal amount) {
        Credentials credentials = blockChainService.getCredentials(fromPriKey);
        java.util.function.Function<BigInteger, RawTransaction> function = getTransferFrom(web3jDto, erc20Address, credentials.getAddress(), to, amount);
        return web3jService.sendTransaction(web3jDto, credentials, function);
    }

    /**
     * 转账封装方法
     * @param web3jDto
     * @param erc20Address
     * @param to
     * @param amount
     * @return
     */
    private java.util.function.Function<BigInteger, RawTransaction> getTransferFrom(Web3jDto web3jDto, String erc20Address, String from, String to, BigDecimal amount) {
        java.util.function.Function<BigInteger, RawTransaction> function = (nonce) -> {
            String methodName = "transferFrom";
            BigInteger value = Convert.toWei(amount, Convert.Unit.fromString(decimals(web3jDto, erc20Address).toString())).toBigInteger();
            //构建调用erc20合约的data
            List<Type> inputParameters = Arrays.asList(
                    new Address(from),
                    new Address(to),
                    new Uint256(value)
            );
            List<TypeReference<?>> outputParameters = Arrays.asList(
                    new TypeReference<Bool>() {
                    }
            );
            String data = EthUtils.builderData(methodName, inputParameters, outputParameters);
            return RawTransaction.createTransaction(nonce, web3jDto.getGasPrice(), web3jDto.getGasLimit(), erc20Address, data);
        };
        return function;
    }

}
